第6週 構造体・列挙型
本日の内容
前回の復習
練習問題解答
2値x,yを受け取って√(x^2+y^2)を返す関数my_hypotを書いてください
code:my_hypot.c
float my_hypot(float x, float y) {
return sqrt(x * x + y * y);
}
再帰でn番目のフィボナッチ数を求める関数fibを書いてください
code:fib.c
int fib(int n) {
if(n < 2) return 1;
return fib(n - 1) + fib(n - 2);
}
余談
計算量が凄まじい(O(2^n))のでメモ化やDPについて調べると良いと思います
構造体について
なぜ構造体が必要なのか
構造体の使い方
列挙型について
列挙型の使い方
構造体について
はじめに
日本語や英語などの自然言語,数学と比べ,プログラミング言語は数十年前に人間が明確な意図を以て設計したという特徴があります.「なぜその概念を導入したのか」という意図をまず把握することにより,プログラミング言語の学習がより行いやすくなるかもしれません.
なぜ構造体が必要なのか
ここでは,まず「なぜ構造体というものが必要なのか」ということについて説明していきます.
第2週 型と演算子では,int, floatなどC言語に存在する値には様々な種類が存在し,それを型と呼んでいました. これまでの週で見てきた例では数値計算などについて扱ったものなど,比較的シンプルなものだったので登場する型も第2週 型と演算子で登場したものを使うだけで十分対応出来ていました. しかし,型の種類がint, floatのような基本的なものだけでは困ってしまう場合があります.例えば,「学生の名前とテストの点数を管理したい」時,以下のように書いてみました.
code:none_structure.c
int main(){
char name_student1[] = "Taro";
int score_student1_test1 = 50;
int score_student1_test2 = 70;
char name_student2[] = "Hanako";
int score_student2_test1 = 90;
int score_student2_test2 = 100;
...
}
この例では,「ある学生の名前」「ある学生の試験1の点数」「ある学生の試験2の点数」を表す変数について,それぞれで扱っています....なんとなく,「学生一人ごと」にその学生の名前や試験の点数をまとめてみたくはありませんか.
この例では学生一人ごとに3つの変数しか使っていないのでそう思わないかもしれません.しかし,例えば「学生がこれまで受けて来た数十個の試験をまとめて管理する」ような時学生一人ごとにまとめず変数をバラバラのまま使うと,学生一人ごとに新しい変数を数十個書くことになり管理がすごくめんどくさくなりそうですよね.また,実際には対象の学生は何十人何百人といますから,さらに書くのがめんどくさくなりそうです.そうなると書き間違いとか書き忘れとか頻発しますよね.
そこで,「int, floatなど基本的な型をユーザが組み合わせて,新しい型を定義できるようになればすごく便利なんじゃない?」
という考え方が生まれてきました.これが,言語が提供する型からユーザが新しい型を構成できるようにする仕組み−構造体(structure)が作られた理由です.
構造体の使い方
では,構造体の具体的な使い方について見ていきましょう.まずは「構造体をどう定義するのか」ということについてです.構造体を定義するための書式を構造体指定子とよび,以下のような書き方をします.
code: structure_specifier.c
struct 識別子{
型名 メンバー名;
型名 メンバー名;
...
};
最初のstructは,構造体を示すキーワードのようなものです.「ここから構造体の定義に入るよ」ということを明示します.
識別子とは,各構造体の定義を一意に指定できるようにするためにつける名前のようなものです.ただし,識別子はなくても構いません.その場合については後述します.より詳しく言えば,識別子とは構造体を識別子の名を持つ新しい型として扱えるようにするものです.これにより,構造体,つまり様々な型を集めて作った新しい型も,int, floatなど基本的な型と同じように扱うことが出来ます.
構造体はユーザが新しく型が定義できるので,ユーザ定義型ともいいます.
メンバとは,各構造体を構成する要素についてのことをいいます.型はint, floatなど基本的なもののほか,別の構造体でもかまいません.それについても後述します.
構造体指定子の範囲はstructから最後の「}」までです.しかし,最後にセミコロン ;をつけることも忘れないようにしましょう.一応,構造体指定子は文なので「}」の後にセミコロンをつける必要があります.
さて,「構造体がなぜ必要なのか」で出てきた,「学生の名前とその学生が受けた試験の点数を管理する」の例を使って実際に構造体を定義してみましょう.ここでは,「どうやって構造体の各メンバの値を設定するのか」「どうやって構造体の各メンバの値を参照するのか」についても一緒に扱います.
code:structure_example1_student.c
int main(){
struct student {
int score_student_test1;
int score_student_test2;
};
struct student student1;
strncpy(student1.name_student, "Taro", sizeof(student1.name_student) / sizeof(char));
student1.score_student_test1 = 50;
student1.score_student_test2 = 70;
struct student student2 = {"Hanako", 90, 100};
printf("name:%s, score_of_tenst1:%d, score_of_tenst2:%d\n", student1.name_student, student1.score_student_test1, student1.score_student_test2);
//name:Taro, score_of_tenst1:50, score_of_tenst2:70
printf("name:%s, score_of_tenst1:%d, score_of_tenst2:%d\n", student2.name_student, student2.score_student_test1, student2.score_student_test2);
//name:Hanako, score_of_tenst1:90, score_of_tenst2:100
}
まず,struct 識別子名 変数名で,その識別子によって指定される構造体を,その変数名をつけて新しく一つ宣言します.
しかし,ただ構造体を新しく一つ宣言しただけでは中身がなくどうしようもないので,新しく宣言した構造体の各メンバに対し初期値を設定する必要があります.
構造体のメンバを指定するには,その変数名に対し.(ドット)演算子を適用します.
変数名.メンバ名
のようにして各メンバの値を参照します.
ここでは,上のstructから始まる構造体指定子で定義したstudent型である変数student1に対し,ドット演算子を用いstudentの各メンバーの値を参照して初期値を設定しています.ここで,
strncpy(str1, str2, 文字数);
とは,文字配列str1に文字配列str2の中身をコピーするという関数です.(詳しいことはポインタを学習してから調べてみましょう)
また,構造体も配列と同じように「{ }」を使って初期値を設定することも出来ます.ここでは,student2に対し「{ }」を用いて初期値を設定しています.ただし,各構造体指定子で記述されているメンバーの順番通りに初期値を設定しないとコンパイルエラーが発生します.
識別子名がない構造体について
「構造体指定子には,識別子名がなくてもかまいません」と前述しました.ここでは,その場合について説明していきます.
識別子名がない場合の構造体を,無名構造体と言います.無名構造体は,型名がない型に名前をつけることができるtypedef指定子を用いることで,型名を与えてあげることができます.こうすることで,構造体を簡潔に利用できるようになります.
code:structure_example2_withtypedef.c
int main(){
typedef struct {
int score_student_test1;
int score_student_test2;
} Student;
Student student1 = {"Taro", 50, 70};
printf("name:%s, score_of_tenst1:%d, score_of_tenst2:%d\n", student1.name_student, student1.score_student_test1, student1.score_student_test2);
//name:Taro, score_of_tenst1:50, score_of_tenst2:70
}
識別子名がある場合と比べて,新しく構造体を一つ宣言する際,structを用いずに済んでいますね.
構造体をメンバに持つ構造体について
今まで出てきた構造体は,int, floatなど基本的な型のみをメンバに持ったものでした.しかしながら,プログラムを書いていく上では,「複数の構造体もまとめて新しい型として使いたいなー」という場面も当然出てきます.ここでは,構造体をメンバにもつ構造体について説明していきます.
code:structure_example3_withstructure.c
int main(){
typedef struct {
double x;
double y;
} Point;
typedef struct {
Point pt;
double velocity;
} Car;
Car car1 = {{0, 0}, 20};
Car car2;
car2.pt.x = 20;
car2.pt.y = 30;
car2.velocity = 0;
printf("x:%f, y:%f, velocity:%f\n", car1.pt.x, car1.pt.y, car1.velocity);
//x:0.000000, y:0.000000, velocity:20.000000
printf("x:%f, y:%f, velocity:%f\n", car2.pt.x, car2.pt.y, car2.velocity);
//x:20.000000, y:30.000000, velocity:0.000000
}
構造体を要素として含む構造体を宣言するときは,他の基本的な型と同じように「型名 メンバ名」とします.また,要素に含まれている構造体のメンバを参照するには,ドット演算子を適切な回数で用います.この例では,Car型に含まれる構造体はPoint型一つだけなので,ドット演算子を二回用いればptの要素を参照することができます.
列挙型について
列挙型の使い方
列挙型とは,一連の識別子にint型の値を割り当てるものです.列挙型は,キーワードenumに続けて識別子名を指定してから,「{}」のなかに識別子をカンマで区切って作成します.それらの識別子は列挙定数とも言います.実際の例を見て確認してみましょう.
code:enumeration_example1.c
int main(){
enum month1 {Janualy, Febraly, March};
typedef enum {May = 5, June, October = 10} month2;
//typedef enum {March = 3, May = 5, June} month2;識別子名「March」が重複してるためこれは定義できない
printf("Jaunualy:%d, Febraly:%d, March:%d\n", Janualy, Febraly, March);
//Jaunualy:0, Febraly:1, March:2
printf("May:%d, June:%d, October:%d\n", May, June, October);
//May:5, June:6, October:10
}
各列挙定数は値を自由に設定することができますが,もし省略した場合一つ前の列挙定数に1を加えた値を取ります.一番初めの列挙定数の値を省略した場合,値は0となります.
また,構造体と構文が似ていることからわかるように,列挙型も「enum 識別子名」とすることで「enum 識別子名」型としたり,typedefを用いて型名を割り当てたりすることができます.
ただ,構造体と異なり,異なる列挙型で定義した列挙定数名を重複して定義することはできません.
ここまで,「複数のものをまとめて一つにする」という動機で開発された構造体というものについて説明しました.ちなみに,この「複数のものをまとめて一つにする」という考え方はプログラミングにおいて基本となる枠組みの一つで,これを発展させていったものがC++におけるクラスの概念,さらには一種のオブジェクト指向と呼ばれる考え方につながっていきます.
それについて興味がある方は,参考文献にある西尾泰和著,「コーディングを支える技術 成り立ちから学ぶプログラミング作法」を読んでみてください.
練習問題
xy平面上の座標を表す構造体Pointを定義してください。
メンバにPointを3つ持つ構造体Tripleを定義してください。
そのTripleが三角形であるかを判定する関数is_triangleを書いてください。
面積が100以下の三角形であるTripleをランダムに生成して返す関数make_triangleを書いてください。
赤橙黄青水緑茶灰の8色の値がある列挙型Colorを定義してください。
TripleとColorをメンバに持つ構造体Triangleを定義してください。
ランダム生成した面積100以下の三角形であるTripleと、過去7回で生成したColorとは違う色をメンバとして持つようなTriangleを生成する関数generateを書いてください。
挑戦問題
参考文献
arton, 「独習C言語 新版」, 翔泳社
西尾泰和, 「コーディングを支える技術 成り立ちから学ぶプログラミング作法」, 技術評論社
柴田望洋,「新・明快C言語 入門編」, SBクリエイティブ
柴田望洋監修・由梨かおる著,「新・解きながら学ぶ C言語」, SBクリエイティブ